-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local c_object = require 'mc.orm.object'
local sml = require 'sml'
local signal = require 'mc.signal'
local ctrl_commu_loss_monitor = require 'ctrl_commu_loss_monitor'
local common_def = require 'common_def'
local log = require 'mc.logging'
local add_event = require 'add_event'
local debounce = require 'mc.debounce.debounce'
local skynet = require 'skynet'

---@class c_battery: c_object
---@field RefController integer
---@field new fun(...): c_battery
-- Battery 类，主键是 RefControllerDeviceName，创建Battery对象前主键必须确认 
local c_battery = c_object('Battery', { 'RefControllerDeviceName' })

local GET_INFO_FROM_RAID <const> = 'get_bbu_info_from_raid'

local map <const> = {
    {'pack_missing', common_def.FC_STORAGE_RAID_BBU, 'RAID controller (%s) BBU pack is missing - %s'},
    {'temperature_high', common_def.FC_STORAGE_RAID_BBU, 'RAID controller (%s) BBU temperature is high - %s'},
    {'voltage_low', common_def.FC_STORAGE_RAID_BBU, 'RAID controller (%s) BBU voltage is low - %s'},
    {'replacepack', common_def.FC_STORAGE_RAID_BBU_REPLACE,
        'RAID controller (%s) BBU replacement is required - %s'},
    {'learn_cycle_failed', common_def.FC_STORAGE_RAID_BBU, 'RAID controller (%s) BBU learn cycle failed - %s'},
    {'learn_cycle_timeout', common_def.FC_STORAGE_RAID_BBU, 'RAID controller (%s) BBU learn cycle timeout - %s'},
    {'predictive_failure', common_def.FC_STORAGE_RAID_BBU,
        'RAID controller (%s) BBU predictive failed and should be replaced - %s'},
    {'remaining_capacity_low', common_def.FC_STORAGE_RAID_BBU,
        'RAID controller (%s) BBU remaining capacity is low - %s'},
    {'no_space', common_def.FC_STORAGE_RAID_BBU, 'RAID controller (%s) BBU no space for cache-offload - %s'},
    {'failed', common_def.FC_STORAGE_RAID_BBU, 'RAID controller (%s) BBU failed - %s'}
}

local function prefix_equal(str, prefix)
    return string.sub(str, 1, string.len(prefix)) == prefix
end

function c_battery:ctor(obj)
    while true do
        skynet.sleep(10)
        -- 如果主键为空或Slot为255或Slot为0h或Slot为占位符$则不加载，等待有效主键分发
        if not prefix_equal(obj.RefControllerDeviceName, "PCIe Card") or
            prefix_equal(obj.RefControllerDeviceName, "PCIe Card 0") or
            prefix_equal(obj.RefControllerDeviceName, "PCIe Card $") or
            prefix_equal(obj.RefControllerDeviceName, "PCIe Card 255") then
        else
            break
        end
    end
    local controller = obj:get_parent()
    self.RefController = controller and controller.Id or 255
    self.pack_missing = 0
    self.temperature_high = 0
    self.voltage_low = 0
    self.replacepack = 0
    self.learn_cycle_failed = 0
    self.learn_cycle_timeout = 0
    self.predictive_failure = 0
    self.remaining_capacity_low = 0
    self.no_space = 0
    self.failed = 0
    self.on_update = signal.new()
    self.midavg = debounce[debounce.DEBOUNCED_MIDAVG].new(4, 20)
    self.cont_bin = debounce[debounce.DEBOUNCED_CONT_BIN].new(40, 5, 0)
end

function c_battery:dtor()
    self:stop_tasks()
end

function c_battery:init()
    c_battery.super.init(self)
    self.State = 255
    self.on_update:on(function(info)
        self:update_battery_info(info)
    end)
end

function c_battery:start()
    local controller_id, ok, ret
    log:notice('Start update controller%s battery info.', self.RefController)
    self:new_task({ GET_INFO_FROM_RAID, self.RefController }):loop(function(task)
        if task.is_exit then
            return
        end
        controller_id = self.RefController
        ok, ret = pcall(sml.get_ctrl_init_state, controller_id)
        if not ok or ret ~= 2 then
            return
        end
        ok, ret = pcall(sml.get_battery_info, self.RefController)
        if not ok then
            if ret ~= common_def.SML_ERR_CTRL_STATUS_INVALID and ret ~= common_def.SML_ERR_NULL_INFTERFACE then
                ctrl_commu_loss_monitor.get_instance():update(true, self.RefController)
            end
            return
        end
        if task.is_exit then
            return
        end
        ctrl_commu_loss_monitor.get_instance():update(false, self.RefController)
        self.on_update:emit(ret)
    end):set_timeout_ms(60000)
end

function c_battery:set_default_values()
    self.State = common_def.INVALID_U8
    self.TemperatureCelsius = 0
    self.Name = ''
end

function c_battery:stop()
    self:stop_tasks()
    log:notice('Stop update controller%s battery info.', self.RefController)
    self:set_default_values()
end

---@param info c_battery_info
function c_battery:update_battery_info(info)
    if info.present == nil then
        return
    end
    
    self.State = info.present
    local temp = self.midavg:get_debounced_val(info.temperature)
    if self.State == 1 then
        -- 处理某些厂商raid卡的BBU温度为负的场景: 将16位无符号数转化为有符号数，如65535转成-1
        local two_bytes_width = temp & common_def.U16_MAX
        local trans_temperature = two_bytes_width >= common_def.SIGNED_BIT and
            (-(common_def.U16_MAX - two_bytes_width + 1)) or two_bytes_width
        info.temperature = (trans_temperature > 255) and 255 or trans_temperature
        self.Name = info.type
    else
        info.temperature = 0
        self.Name = ''
    end
    -- 温度获取失败告警做消抖处理
    local read_failed = self.cont_bin:get_debounced_val((info.temperature == 255) and 1 or 0)
    self.TemperatureCelsius = (read_failed == 1) and 0x7fff or info.temperature
    self:update_battery_health_status_info(info)
    self:check_fault(info)
end

--[[ 
将BBU状态更新到资源树属性
bit0-电压过低
bit1-需要更换BBU
bit2-电量校准失败
bit3-电量校准超时
bit4-预故障
bit5-剩余容量低
bit6-没有用于缓存卸载的空间
others:resrved
--]]
function c_battery:update_battery_health_status_info(info)
    local merged_flag = 0
    merged_flag = merged_flag | (info.voltage_low == 1 and info.voltage_low or 0)
    merged_flag = merged_flag | (info.replacepack == 1 and (info.replacepack << 1) or 0)
    merged_flag = merged_flag | (info.learn_cycle_failed == 1 and (info.learn_cycle_failed << 2) or 0)
    merged_flag = merged_flag | (info.learn_cycle_timeout == 1 and (info.learn_cycle_timeout << 3) or 0)
    merged_flag = merged_flag | (info.predictive_failure == 1 and (info.predictive_failure << 4) or 0)
    merged_flag = merged_flag | (info.remaining_capacity_low == 1 and (info.remaining_capacity_low << 5) or 0)
    merged_flag = merged_flag | (info.no_space == 1 and (info.no_space << 6) or 0)
    self.HealthStatus = merged_flag
end

function c_battery:check_fault(info)
    for i = 1, #map do
        -- 如果BBU不在位但是要触发告警，说明数据异常。TFM卡在位，电容不在位(Pack Missing)，storelib会返回BBU不在位。因此排除这种情况
        if self.State ~= 1 and info[map[i][1]] == 1 and map[i][1] ~= 'pack_missing' then
            return
        end

        if self[map[i][1]] ~= info[map[i][1]] then
            self[map[i][1]] = info[map[i][1]]
            log:maintenance(log.MLOG_ERROR, map[i][2], map[i][3], self.RefControllerDeviceName,
                info[map[i][1]] == 1 and 'Assert' or 'Deasserted')
            if map[i][1] == 'voltage_low' then
                add_event.get_instance().generate_bbu_lower_voltage(info[map[i][1]] == 1 and 'true' or 'false', 
                    tostring(self.RefControllerSlotId), self.RefControllerTypeId)
            end
        end
    end

    self.Fault = ((self.predictive_failure + self.replacepack) ~= 0) and 1 or 0
end

return c_battery
